﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using DiscoveryLogic.Common.Utilities;
using DiscoveryLogic.DataViz.NetworkDiagram.NetworkData;
using EntityType=DiscoveryLogic.DataViz.NetworkDiagram.NetworkData.EntityType;

namespace DiscoveryLogic.DataViz.NetworkDiagram
{
    /// <summary>
    /// 
    /// </summary>
    public class Network
    {
        #region ctor
        /// <summary>
        /// 
        /// </summary>
        public Network()
        {
            this._NetData=new NetworkDataObject();
            this._VizSetting=new NetworkVisualSetting();
            this._FieldStats=new Dictionary<string, FieldStat>();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="networkName"></param>
        /// <param name="provider"></param>
        /// <param name="netDO"></param>
        /// <param name="distanceCalc"></param>
        /// <param name="entityType"></param>
        /// <param name="linkType"></param>
        /// <param name="isInferred"></param>
        public Network(
            string networkName, SetDataProvider provider, 
            NetworkDataObject netDO,
            ILinkDistanceCalculator distanceCalc,
            EntityType entityType, LinkType linkType, bool isInferred)
        {
            this._NetworkName = networkName;
            this._DataProvider = provider;
            this._NetData = netDO;
            this._DistanceCalc = distanceCalc;
            this._entityType = entityType;
            this._LinkType = linkType;
            this._IsInferred = isInferred;
            this._VizSetting = new NetworkVisualSetting();
            this._FieldStats=new Dictionary<string, FieldStat>();
        }
        #endregion

        #region props
        private NetworkVisualSetting _VizSetting;
        /// <summary></summary>
        public NetworkVisualSetting VizSetting
        {
            get
            {
                return this._VizSetting;
            }
            set
            {
                this._VizSetting = value;
            }
        }

        private string _NetworkName;
        /// <summary>
        /// unique name within workspace
        /// </summary>
        public string NetworkName
        {
            get
            {
                return _NetworkName;
            }
            set
            {
                _NetworkName = value;
            }
        }

        private SetDataProvider _DataProvider;
        /// <summary></summary>
        public SetDataProvider DataProvider
        {
            get
            {
                return this._DataProvider;
            }
            set
            {
                this._DataProvider = value;
            }
        }

        private EntityType _entityType;
        /// <summary>
        /// can be used to switch vertice shape/color
        /// </summary>
        public EntityType EntityType
        {
            get
            {
                return _entityType;
            }
            set
            {
                _entityType = value;
            }
        }

        private LinkType _LinkType;
        /// <summary>
        /// can be used to switch edge color
        /// </summary>
        public LinkType RelationType
        {
            get
            {
                return _LinkType;
            }
            set
            {
                _LinkType = value;
            }
        }

        private NetworkDataObject _NetData;
        /// <summary></summary>
        public NetworkDataObject NetData
        {
            get
            {
                return this._NetData;
            }
            set
            {
                this._NetData = value;
            }
        }

        private ILinkDistanceCalculator _DistanceCalc;
        /// <summary>
        /// 
        /// </summary>
        public ILinkDistanceCalculator DistanceCalculator
        {
            get
            {
                return _DistanceCalc;
            }
            set
            {
                _DistanceCalc = value;
            }
        }

        private bool _IsInferred;
        /// <summary>
        /// whether the relation is inferred (soft link that has possibility)
        /// can be used to switch edge line style (solid, dash)
        /// </summary>
        public bool IsInferred
        {
            get
            {
                return _IsInferred;
            }
            set
            {
                _IsInferred = value;
            }
        }

        private double _AttractingForce=2.0;
        /// <summary></summary>
        public double AttractingForce
        {
            get
            {
                return this._AttractingForce;
            }
            set
            {
                this._AttractingForce = value;
            }
        }

        private double _RepellingForce=20.0;
        /// <summary></summary>
        public double RepellingForce
        {
            get
            {
                return this._RepellingForce;
            }
            set
            {
                this._RepellingForce = value;
            }
        }

        private Dictionary<string,FieldStat> _FieldStats;
        /// <summary></summary>
        public Dictionary<string,FieldStat> FieldStats
        {
            get
            {
                return this._FieldStats;
            }
            set
            {
                this._FieldStats = value;
            }
        }
        #endregion

        #region meta data
        /// <summary>
        /// 
        /// </summary>
        /// <param name="networkName"></param>
        /// <returns></returns>
        public static Network ReadNetwork(string networkName)
        {
            Network network = new Network();
            string appFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            appFolder = Path.Combine(appFolder, "Network Data");
            if (!Directory.Exists(appFolder))
                Directory.CreateDirectory(appFolder);
            string networkDataFilePath = Path.Combine(appFolder, networkName + ".net");
            if (!File.Exists(networkDataFilePath))
                throw new Exception("Network data file does not exist");

            XmlDocument xDoc = new XmlDocument();
            xDoc.Load(networkDataFilePath);
            XmlNode root = xDoc.DocumentElement;
            network.NetworkName = XmlDataUtil.GetAttributeValue(root, "NetworkName", "");
            Assembly assembly = typeof(Network).Assembly;
            //string dataobjTypeName = XmlDataUtil.GetAttributeValue(root, "DataObjType", "");
            //network.DataObjectType = assembly.GetType(dataobjTypeName);
            string calcTypeName = XmlDataUtil.GetAttributeValue(root, "CalcType", "");
            Type calcType = assembly.GetType(calcTypeName);
            ILinkDistanceCalculator calc = (ILinkDistanceCalculator)Activator.CreateInstance(calcType);
            network.DistanceCalculator = calc;
            network.EntityType =
                (EntityType)
                Enum.Parse(typeof(EntityType),
                           XmlDataUtil.GetAttributeValue(root, "EntityType", EntityType.Project.ToString()));
            network.RelationType =
                (LinkType)
                Enum.Parse(typeof(LinkType),
                           XmlDataUtil.GetAttributeValue(root, "LinkType", LinkType.Citation.ToString()));
            network.IsInferred = bool.Parse(XmlDataUtil.GetAttributeValue(root, "IsInferred", false.ToString()));
            // provider
            XmlNode providerNode = root.SelectSingleNode("Provider");
            SetDataProvider provider = new SetDataProvider();
            provider.SourceType =
                (SetDataSourceType)
                Enum.Parse(typeof(SetDataSourceType),
                           XmlDataUtil.GetAttributeValue(providerNode, "SourceType", SetDataSourceType.DB.ToString()));
            provider.DataSourcePath = XmlDataUtil.GetAttributeValue(providerNode, "SrcPath", "");
            provider.RecordListPath = XmlDataUtil.GetAttributeValue(providerNode, "RecPath", "");
            provider.KeyFieldName = XmlDataUtil.GetAttributeValue(providerNode, "KeyField", "");
        	provider.LabelField = XmlDataUtil.GetAttributeValue(providerNode, "LabelField", "");
            XmlNodeList fieldNodes = providerNode.SelectNodes("Fields/Field");
            provider.FieldPaths = new Dictionary<string, string>();
            if (fieldNodes != null && fieldNodes.Count > 0)
            {
                foreach (XmlNode fieldNode in fieldNodes)
                {
                    string fieldName = XmlDataUtil.GetAttributeValue(fieldNode, "FieldName", "");
                    string propName = XmlDataUtil.GetAttributeValue(fieldNode, "PropertyName", "");
                    provider.FieldPaths.Add(fieldName, propName);
                }
            }
            network.DataProvider = provider;

            // network meta data
            XmlNode netMetaDataNode = root.SelectSingleNode("MetaData");
            NetworkDataObject netMetaData = NetworkDataObject.ReadMetaData(netMetaDataNode);
            network.NetData = netMetaData;

            // viz
            XmlNode vizNode = root.SelectSingleNode("VizSetting");
            NetworkVisualSetting vizSetting = new NetworkVisualSetting();
            vizSetting.VerticeSize = new SizeF(
                float.Parse(XmlDataUtil.GetAttributeValue(vizNode, "VerticeSizeW", "20")),
                float.Parse(XmlDataUtil.GetAttributeValue(vizNode, "VerticeSizeH", "20")));
            vizSetting.VerticeSizeCode = XmlDataUtil.GetAttributeValue(vizNode, "VerticeSizeCode", "");
            vizSetting.VerticeColor = Color.FromArgb(
                int.Parse(XmlDataUtil.GetAttributeValue(vizNode, "VerticeColorA", "150")),
                int.Parse(XmlDataUtil.GetAttributeValue(vizNode, "VerticeColorR", "0")),
                int.Parse(XmlDataUtil.GetAttributeValue(vizNode, "VerticeColorG", "0")),
                int.Parse(XmlDataUtil.GetAttributeValue(vizNode, "VerticeColorB", "255")));
            vizSetting.VerticeColorCode = XmlDataUtil.GetAttributeValue(vizNode, "VerticeColorCode", "");
            vizSetting.VerticeShape = (VerticeShape)Enum.Parse(
                                                  typeof(VerticeShape),
                                                  XmlDataUtil.GetAttributeValue(
                                                      vizNode, "VerticeShape", VerticeShape.Circle.ToString()));
            vizSetting.VerticeShapeCode = XmlDataUtil.GetAttributeValue(vizNode, "VerticeShapeCode", "");
            vizSetting.EdgeColor = Color.FromArgb(
                int.Parse(XmlDataUtil.GetAttributeValue(vizNode, "EdgeColorA", "150")),
                int.Parse(XmlDataUtil.GetAttributeValue(vizNode, "EdgeColorR", "255")),
                int.Parse(XmlDataUtil.GetAttributeValue(vizNode, "EdgeColorG", "0")),
                int.Parse(XmlDataUtil.GetAttributeValue(vizNode, "EdgeColorB", "0")));
            vizSetting.EdgeColorCode = XmlDataUtil.GetAttributeValue(vizNode, "EdgeColorCode", "");
            vizSetting.EdgeThickness = float.Parse(XmlDataUtil.GetAttributeValue(vizNode, "EdgeThickness", "2"));
            vizSetting.EdgeThicknessCode = XmlDataUtil.GetAttributeValue(vizNode, "EdgeThicknessCode", "");
            vizSetting.VerticeTransparency = int.Parse(XmlDataUtil.GetAttributeValue(vizNode, "VerticeTransparency", "100"));
            vizSetting.VerticeTranspacencyCode = XmlDataUtil.GetAttributeValue(vizNode, "VerticeTranspacencyCode", "");
            vizSetting.EdgeTranspacency = int.Parse(XmlDataUtil.GetAttributeValue(vizNode, "EdgeTranspacency", "100"));
            vizSetting.EdgeTranspacencyCode = XmlDataUtil.GetAttributeValue(vizNode, "EdgeTranspacencyCode", "");
        	vizSetting.LineStyle =
        		(EdgeLineStyle)
        		Enum.Parse(typeof (EdgeLineStyle),
        		           XmlDataUtil.GetAttributeValue(vizNode, "LineStyle", EdgeLineStyle.Solid.ToString()));
        	vizSetting.LineStyleCode = XmlDataUtil.GetAttributeValue(vizNode, "LineStyleCode", "");
			
            network.VizSetting = vizSetting;

            // forces 
            network.AttractingForce = double.Parse(XmlDataUtil.GetAttributeValue(root, "AttractingForce", "2"));
            network.RepellingForce = double.Parse(XmlDataUtil.GetAttributeValue(root, "RepellingForce", "12"));

            return network;
        }
        #endregion

        #region all data 
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dtData"></param>
        /// <param name="links"></param>
        public void PopulateNetworkData(ref DataTable dtData, ref List<LinkDataObject> links)
        {
            dtData = new DataTable();
            foreach(string fieldName in this.NetData.FieldDataTypes.Keys)
            {
                dtData.Columns.Add(fieldName, SimpleDataTypeUtil.ToSystemType(this.NetData.FieldDataTypes[fieldName]));
            }
            DataTable dtSrc = this._DataProvider.GetDataFromProvider();
            if (dtSrc == null || dtSrc.Rows.Count == 0)
                throw new Exception("Unable to parse data");
            Dictionary<string, int> fieldIndexes = new Dictionary<string, int>();
            for (int i = 0; i < dtSrc.Columns.Count; i++)
            {
                string colName = dtSrc.Columns[i].ColumnName;
                if (this._DataProvider.FieldPaths != null && this._DataProvider.FieldPaths.ContainsKey(colName))
                {
                    string fieldName = this._DataProvider.FieldPaths[colName];
                    fieldIndexes.Add(fieldName, i);
                }
            }
            Dictionary<string, SimpleDataType> collectionTypeProps = this.NetData.GetCollectionTypeMembers();
            List<string> keysAdded=new List<string>();
            string srcKeyFieldName = string.Empty;
            SqlDbType keyDataType = FieldDataType.ToSqlDbType(this.NetData.FieldDataTypes[this.NetData.PkFieldName]);
            foreach(string srcField in this.NetData.FieldNameMappings.Keys)
            {
                if(this.NetData.FieldNameMappings[srcField]==this.NetData.PkFieldName)
                {
                    srcKeyFieldName = srcField;
                    break;
                }
            }
            foreach (DataRow dr in dtSrc.Rows)
            {
                if(dr[srcKeyFieldName]==null || dr[srcKeyFieldName]==DBNull.Value)
                    continue;
                string keyValue = dr[srcKeyFieldName].ToString();
                if (!keysAdded.Contains(keyValue))
                {
                    DataRow drTgt = dtData.NewRow();
                    foreach (string srcFieldName in this.NetData.FieldNameMappings.Keys)
                    {
                        if (dtSrc.Columns.Contains(srcFieldName) &&
                            dtData.Columns.Contains(this.NetData.FieldNameMappings[srcFieldName]))
                        {
                            object propValue = dr[srcFieldName];
                            string tgtFieldName = this.NetData.FieldNameMappings[srcFieldName];
                            if (collectionTypeProps.ContainsKey(tgtFieldName))
                            {
                                if (collectionTypeProps[tgtFieldName] == SimpleDataType.IntListType)
                                {
                                    List<int> propValues = new List<int>();
                                    propValues.Add(int.Parse(propValue.ToString()));
                                    drTgt[tgtFieldName] = propValues;
                                }
                                else
                                {
                                    List<string> propValues = new List<string>();
                                    propValues.Add(propValue.ToString());
                                    drTgt[tgtFieldName] = propValues;
                                }

                            }
                            else
                            {
                                drTgt[tgtFieldName] = propValue;
                            }
                        }
                    }
                    dtData.Rows.Add(drTgt);
                    keysAdded.Add(keyValue);
                }
                else
                {
                    DataRow drExisting = dtData.Select(string.Format("{0}={1}", 
                        this.NetData.PkFieldName, 
                        FieldDataType.ToMssqlDataValue(keyDataType, keyValue)))[0];
                    foreach(string srcFieldName in this.NetData.FieldNameMappings.Keys)
                    {
                        string tgtFieldName = this.NetData.FieldNameMappings[srcFieldName];
                        if(collectionTypeProps.ContainsKey(tgtFieldName) && 
                            dtSrc.Columns.Contains(srcFieldName) &&
                            dtData.Columns.Contains(tgtFieldName))
                        {
                            object propValue = dr[srcFieldName];
                            if (collectionTypeProps[tgtFieldName] == SimpleDataType.IntListType)
                            {
                                List<int> propValues = drExisting[tgtFieldName] as List<int>;
                                propValues.Add(int.Parse(propValue.ToString()));
                                drExisting[tgtFieldName] = propValues;
                            }
                            else
                            {
                                List<string> propValues = drExisting[tgtFieldName] as List<string>;
                                propValues.Add(propValue.ToString());
                                drExisting[tgtFieldName] = propValues;
                            }
                        }
                    }
                }
            }
            
            links=new List<LinkDataObject>();
            ILinkDistanceCalculator distanceCalc = this.DistanceCalculator;
            distanceCalc.InitFieldMappings(this.NetData.DistanceCalcArgumentMappings);
            for(int i=0;i<dtData.Rows.Count;i++)
            {
                DataRow dr1 = dtData.Rows[i];
                for (int k = i + 1; k < dtData.Rows.Count; k++)
                {
                    DataRow dr2 = dtData.Rows[k];
                    double? distance = this.DistanceCalculator.CalculateLinkDistance(dr1, dr2);
                    if (distance.HasValue)
                    {
                        LinkDataObject linkDO = new LinkDataObject(
                            this.NetData.EntityTypeName, this.NetData.EntityTypeName, 
                            StringUtil.ToString(this.NetData.SelfLinkFromFields.ToArray()),
                            StringUtil.ToString(this.NetData.SelfLinkToFields.ToArray()),
                            dr1[this.NetData.PkFieldName].ToString(),
                            dr2[this.NetData.PkFieldName].ToString(),
                            distance.Value);
                        links.Add(linkDO);
                    }
                }
            }
        }

        /// <summary>
        /// this is mostly called by background agent to collect data for display
        /// </summary>
        public void Save()
        {
            DataTable dtData = new DataTable(this.NetData.EntityTypeName);
            List<LinkDataObject> links = new List<LinkDataObject>();
            this.PopulateNetworkData(ref dtData, ref links);

            string appFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            appFolder = Path.Combine(appFolder, "Network Data");
            if (!Directory.Exists(appFolder))
                Directory.CreateDirectory(appFolder);
            string networkDataFilePath = Path.Combine(appFolder, this._NetworkName + ".net");
            if(File.Exists(networkDataFilePath))
                File.Delete(networkDataFilePath);

            XmlDocument xDoc = new XmlDocument();
            xDoc.LoadXml("<Network></Network>");
            XmlNode root = xDoc.DocumentElement;
            XmlDataUtil.UpdateAttribute(ref xDoc, root, "NetworkName",this.NetworkName);
            XmlDataUtil.UpdateAttribute(ref xDoc, root, "CalcType",this.DistanceCalculator.GetType().FullName);
            XmlDataUtil.UpdateAttribute(ref xDoc, root, "EntityType",this.EntityType.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, root, "LinkType",this.RelationType.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, root, "IsInferred",this.IsInferred.ToString());
            // provider
            XmlNode providerNode = XmlDataUtil.AddElement(ref xDoc, root, "Provider");
            XmlDataUtil.UpdateAttribute(ref xDoc, providerNode, "SourceType",this.DataProvider.SourceType.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, providerNode, "SrcPath",this.DataProvider.DataSourcePath);
            XmlDataUtil.UpdateAttribute(ref xDoc, providerNode, "RecPath",this.DataProvider.RecordListPath);
            XmlDataUtil.UpdateAttribute(ref xDoc, providerNode, "KeyField",this.DataProvider.KeyFieldName);
			XmlDataUtil.UpdateAttribute(ref xDoc, providerNode, "LabelField", this.DataProvider.LabelField);
            XmlNode fieldNodes = XmlDataUtil.AddElement(ref xDoc, providerNode, "Fields");
            foreach(string fieldPath in this.DataProvider.FieldPaths.Keys)
            {
                XmlNode fieldNode = XmlDataUtil.AddElement(ref xDoc, fieldNodes, "Field");
                XmlDataUtil.UpdateAttribute(ref xDoc, fieldNode, "FieldName",fieldPath);
                XmlDataUtil.UpdateAttribute(ref xDoc, fieldNode, "PropertyName",this.DataProvider.FieldPaths[fieldPath]);
            }
            // meta data
            XmlNode metaDataNode = XmlDataUtil.AddElement(ref xDoc, root, "MetaData");
            this.NetData.SaveMetaData(ref xDoc, metaDataNode);

            // data 
            XmlNode tableNode = XmlDataUtil.AddElement(ref xDoc, root, "Table");
            this.NetData.SaveData(ref xDoc, tableNode, dtData);

            // links
            XmlNode linkDataParentNode = XmlDataUtil.AddElement(ref xDoc, root, "LinkDataCollection");
            foreach(LinkDataObject l in links)
            {
                XmlNode linkDataNode = XmlDataUtil.AddElement(ref xDoc, linkDataParentNode, "LinkData");
                l.Save(ref xDoc, linkDataNode);
            }

            // stats
            this.FieldStats = FieldStatsUtil.GetFieldStats(dtData);
            if(this.FieldStats !=null && this.FieldStats.Count>0)
            {
                XmlNode fieldStatNodes = XmlDataUtil.AddElement(ref xDoc, root, "FieldStats");
                foreach(FieldStat fieldStat in this.FieldStats.Values)
                {
                    XmlNode fieldStatNode = XmlDataUtil.AddElement(ref xDoc, fieldStatNodes, "FieldStat");
                    XmlDataUtil.UpdateAttribute(ref xDoc, fieldStatNode,"FieldName",fieldStat.FieldName);
                    XmlDataUtil.UpdateAttribute(ref xDoc, fieldStatNode,"DataType",fieldStat.DataType.ToString());
                    XmlDataUtil.UpdateAttribute(ref xDoc, fieldStatNode, "Count", fieldStat.Count.ToString());
                    XmlDataUtil.UpdateAttribute(ref xDoc, fieldStatNode,"DistinctCount",fieldStat.DistinctValueCount.ToString());
                    XmlDataUtil.UpdateAttribute(ref xDoc, fieldStatNode, "MissingCount",fieldStat.MissingValueCount.ToString());

                    if(fieldStat.MaxValue !=null)
                        XmlDataUtil.UpdateAttribute(ref xDoc, fieldStatNode, "Max",fieldStat.MaxValue.ToString());
                    if(fieldStat.MinValue !=null)
                        XmlDataUtil.UpdateAttribute(ref xDoc, fieldStatNode, "Min",fieldStat.MinValue.ToString());
                    if(fieldStat.Mode !=null)
                        XmlDataUtil.UpdateAttribute(ref xDoc, fieldStatNode, "Mode",fieldStat.Mode.ToString());
                    if(fieldStat.Average !=null)
                        XmlDataUtil.UpdateAttribute(ref xDoc, fieldStatNode, "Avg",fieldStat.Average.ToString());
                }
            }

            // viz
            XmlNode vizSettingNode = XmlDataUtil.AddElement(ref xDoc, root, "VizSetting");
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "VerticeSizeW", this.VizSetting.VerticeSize.Width.ToString());
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "VerticeSizeH", this.VizSetting.VerticeSize.Height.ToString());
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "VerticeSizeCode", this.VizSetting.VerticeSizeCode);
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "VerticeColorA", this.VizSetting.VerticeColor.A.ToString());
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "VerticeColorR", this.VizSetting.VerticeColor.R.ToString());
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "VerticeColorG", this.VizSetting.VerticeColor.G.ToString());
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "VerticeColorB", this.VizSetting.VerticeColor.B.ToString());
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "VerticeColorCode", this.VizSetting.VerticeColorCode);
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "VerticeShape", this.VizSetting.VerticeShape.ToString());
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "VerticeShapeCode", this.VizSetting.VerticeShapeCode);
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "EdgeColorA", this.VizSetting.EdgeColor.A.ToString());
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "EdgeColorR", this.VizSetting.EdgeColor.R.ToString());
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "EdgeColorG", this.VizSetting.EdgeColor.G.ToString());
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "EdgeColorB", this.VizSetting.EdgeColor.B.ToString());
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "EdgeColorCode", this.VizSetting.EdgeColorCode);
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "EdgeThickness", this.VizSetting.EdgeThickness.ToString());
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "EdgeThicknessCode", this.VizSetting.EdgeThicknessCode);
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "VerticeTransparency", this.VizSetting.VerticeTransparency.ToString());
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "VerticeTranspacencyCode", this.VizSetting.VerticeTranspacencyCode);
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "EdgeTranspacency", this.VizSetting.EdgeTranspacency.ToString());
            XmlDataUtil.UpdateAttribute(
                ref xDoc, vizSettingNode, "EdgeTranspacencyCode", this.VizSetting.EdgeTranspacencyCode);
			XmlDataUtil.UpdateAttribute(ref xDoc, vizSettingNode, "LineStyle", this.VizSetting.LineStyle.ToString());
			XmlDataUtil.UpdateAttribute(ref xDoc, vizSettingNode, "LineStyleCode", this.VizSetting.LineStyleCode);
            
            // forces 
            XmlDataUtil.UpdateAttribute(ref xDoc, root, "AttractingForce",this.AttractingForce.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, root, "RepellingForce",this.RepellingForce.ToString());

            xDoc.Save(networkDataFilePath);
        }

        /// <summary>
        /// read from disk
        /// </summary>
        public static void ReadData(string networkName, ref DataTable dtNetData, ref List<LinkDataObject> links)
        {
            string appFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            appFolder = Path.Combine(appFolder, "Network Data");
            if (!Directory.Exists(appFolder))
                Directory.CreateDirectory(appFolder);
            string networkDataFilePath = Path.Combine(appFolder, networkName + ".net");
            if (!File.Exists(networkDataFilePath))
                throw new Exception("Network data file does not exist");

            XmlDocument xDoc=new XmlDocument();
            xDoc.Load(networkDataFilePath);
            XmlNode root = xDoc.DocumentElement;
            XmlNode metaDataNode = root.SelectSingleNode("MetaData");
            NetworkDataObject netDO = NetworkDataObject.ReadMetaData(metaDataNode);
            XmlNode tableNode = root.SelectSingleNode("Table");
            dtNetData = netDO.ReadData(tableNode);

            links=new List<LinkDataObject>();
            XmlNodeList linkDataNodes = root.SelectNodes("LinkDataCollection/LinkData");
            if(linkDataNodes !=null && linkDataNodes.Count>0)
            {
                foreach (XmlNode linkDataNode in linkDataNodes)
                {
                    LinkDataObject linkDO = new LinkDataObject(
                        string.Empty, string.Empty, string.Empty, string.Empty, string.Empty, string.Empty,
                        double.PositiveInfinity);
                    linkDO = linkDO.Read(linkDataNode);
                    links.Add(linkDO);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="networkName"></param>
        /// <param name="fieldStats"></param>
        public static void ReadDataStats(string networkName, ref Dictionary<string, FieldStat> fieldStats)
        {
            string appFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            appFolder = Path.Combine(appFolder, "Network Data");
            if (!Directory.Exists(appFolder))
                Directory.CreateDirectory(appFolder);
            string networkDataFilePath = Path.Combine(appFolder, networkName + ".net");
            if (!File.Exists(networkDataFilePath))
                throw new Exception("Network data file does not exist");

            XmlDocument xDoc = new XmlDocument();
            xDoc.Load(networkDataFilePath);

            fieldStats=new Dictionary<string, FieldStat>();
            XmlNodeList fieldStatNodes = xDoc.SelectNodes("//FieldStats/FieldStat");
            if(fieldStatNodes !=null && fieldStatNodes.Count>0)
            {
                foreach(XmlNode statNode in fieldStatNodes)
                {
                    FieldStat fieldStat=new FieldStat();
                    fieldStat.FieldName = XmlDataUtil.GetAttributeValue(statNode, "FieldName", "");
                    fieldStat.DataType = (SimpleDataType) Enum.Parse(
                                                              typeof (SimpleDataType),
                                                              XmlDataUtil.GetAttributeValue(
                                                                  statNode, "DataType",
                                                                  SimpleDataType.TextType.ToString()));
                    fieldStat.Count = int.Parse(XmlDataUtil.GetAttributeValue(statNode, "Count", "0"));
                    fieldStat.DistinctValueCount =
                        int.Parse(XmlDataUtil.GetAttributeValue(statNode, "DistinctCount", "0"));
                    fieldStat.MissingValueCount = int.Parse(
                        XmlDataUtil.GetAttributeValue(statNode, "MissingCount", "0"));

                    if(statNode.Attributes.GetNamedItem("Max")!=null)
                    {
                        string max = XmlDataUtil.GetAttributeValue(statNode, "Max", "");
                        fieldStat.MaxValue = SimpleDataTypeUtil.ToDbValue(fieldStat.DataType, max);
                    }
                    if (statNode.Attributes.GetNamedItem("Min") != null)
                    {
                        string min = XmlDataUtil.GetAttributeValue(statNode, "Min", "");
                        fieldStat.MinValue = SimpleDataTypeUtil.ToDbValue(fieldStat.DataType, min);
                    }
                    if (statNode.Attributes.GetNamedItem("Mode") != null)
                    {
                        string mode = XmlDataUtil.GetAttributeValue(statNode, "Mode", "");
                        fieldStat.Mode = SimpleDataTypeUtil.ToDbValue(fieldStat.DataType, mode);
                    }
                    if (statNode.Attributes.GetNamedItem("Avg") != null)
                    {
                        string avg = XmlDataUtil.GetAttributeValue(statNode, "Avg", "");
                        fieldStat.Average = SimpleDataTypeUtil.ToDbValue(fieldStat.DataType, avg);
                    }
                    fieldStats.Add(fieldStat.FieldName, fieldStat);
                }
            }
        }
        #endregion

        #region select 
        /// <summary>
        /// 
        /// </summary>
        /// <param name="keys"></param>
        /// <returns></returns>
        public DataTable SelectByKey(List<string> keys)
        {
            DataTable dtNet=new DataTable();
            List<LinkDataObject> links=new List<LinkDataObject>();
            this.PopulateNetworkData(ref dtNet, ref links);
            if(dtNet !=null)
            {
                DataTable dtResult = dtNet.Clone();
                foreach(DataRow dr in dtNet.Rows)
                {
                    string key = dr[this.NetData.PkFieldName].ToString();
                    if(keys.Contains(key))
                    {
                        DataRow drResult = dtResult.NewRow();
                        for(int i=0;i<dtResult.Columns.Count;i++)
                        {
                            drResult[i] = dr[i];
                        }
                        dtResult.Rows.Add(drResult);
                    }
                }
                return dtResult;
            }
            else
                return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public Dictionary<string, object> SelectByKey(string key)
        {
            Dictionary<string,object> fieldValues=new Dictionary<string, object>();
            string appFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            appFolder = Path.Combine(appFolder, "Network Data");
            if (!Directory.Exists(appFolder))
                Directory.CreateDirectory(appFolder);
            string networkDataFilePath = Path.Combine(appFolder, this.NetworkName + ".net");
            if (!File.Exists(networkDataFilePath))
                throw new Exception("Network data file does not exist");

            Dictionary<string, SimpleDataType> collectionTypeProps = this.NetData.GetCollectionTypeMembers();
            Dictionary<string, SimpleDataType> fieldDataTypes = this.NetData.FieldDataTypes;
            XmlDocument xDoc = new XmlDocument();
            xDoc.Load(networkDataFilePath);
            XmlNode root = xDoc.DocumentElement;
            XmlNode tableNode = root.SelectSingleNode("Table");
            XmlNodeList pkNodes = tableNode.SelectNodes("Row/" + this.NetData.PkFieldName);
            foreach (XmlNode pkNode in pkNodes)
            {
                if(pkNode.InnerText !=null && pkNode.InnerText==key)
                {
                    XmlNode rowNode = pkNode.ParentNode;
                    foreach (string fieldName in fieldDataTypes.Keys)
                    {
                        XmlNode fieldNode = rowNode.SelectSingleNode(fieldName);
                        if (fieldNode != null && fieldNode.InnerText != null)
                        {
                            string propValue = fieldNode.InnerText;
                            if (collectionTypeProps.ContainsKey(fieldName))
                            {
                                if (collectionTypeProps[fieldName] == SimpleDataType.IntListType)
                                {
                                    int[] intArray = IntUtil.ToArray(propValue);
                                    List<int> propValues = new List<int>();
                                    propValues.AddRange(intArray);
                                    fieldValues.Add(fieldName, propValues);
                                }
                                else
                                {
                                    string[] intArray = StringUtil.Split(propValue);
                                    List<string> propValues = new List<string>();
                                    propValues.AddRange(intArray);
                                    fieldValues.Add(fieldName, propValues);
                                }
                            }
                            else
                            {
                                object propValue2 = SimpleDataTypeUtil.ToDbValue(fieldDataTypes[fieldName], propValue.ToString());
                                fieldValues.Add(fieldName, propValue2);
                            }
                        }
                    }
                    break;
                }
            }

            return fieldValues;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fieldName"></param>
        /// <param name="allowedValues"></param>
        /// <returns></returns>
        public List<string> GetObjectKeysUsingFieldFilter(string fieldName, List<string> allowedValues)
        {
            List<string> keys = new List<string>();
            DataTable dtNet = new DataTable();
            string pkFieldName = this.NetData.PkFieldName;
            List<LinkDataObject> links = new List<LinkDataObject>();
            this.PopulateNetworkData(ref dtNet, ref links);
            if (dtNet != null && dtNet.Columns.Contains(fieldName))
            {
                SqlDbType dbType = SqlDbTypeUtil.ToSqlDbType(dtNet.Columns[fieldName].DataType);
                List<string> inVals=new List<string>();
                foreach(string fieldVal in allowedValues)
                {
                    string inVal =
                        FieldDataType.ToMssqlDataValue(dbType, fieldVal);
                    inVals.Add(inVal);
                }
                DataRow[] drs =
                    dtNet.Select(string.Format("{0} in {1}", fieldName, StringUtil.ToString(inVals.ToArray())));
                if(drs !=null && drs.Length>0)
                {
                    foreach(DataRow dr in drs)
                    {
                        string key = dr[pkFieldName].ToString();
                        keys.Add(key);
                    }
                }
            }
            return keys;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fieldName"></param>
        /// <param name="max"></param>
        /// <param name="min"></param>
        /// <returns></returns>
        public List<string> GetObjectKeysUsingFieldRangeFilter(string fieldName, object min, object max)
        {
            List<string> keys = new List<string>();
            DataTable dtNet = new DataTable();
            string pkFieldName = this.NetData.PkFieldName;
            List<LinkDataObject> links = new List<LinkDataObject>();
            this.PopulateNetworkData(ref dtNet, ref links);
            if (dtNet != null && dtNet.Columns.Contains(fieldName))
            {
                SqlDbType dbType = SqlDbTypeUtil.ToSqlDbType(dtNet.Columns[fieldName].DataType);
                string minVal =
                        FieldDataType.ToMssqlDataValue(dbType, min.ToString());
                string maxVal =
                        FieldDataType.ToMssqlDataValue(dbType, max.ToString());
                DataRow[] drs =
                    dtNet.Select(
                        string.Format("{0} >= {1} AND {0} <= {2}", fieldName, minVal, maxVal));
                if (drs != null && drs.Length > 0)
                {
                    foreach (DataRow dr in drs)
                    {
                        string key = dr[pkFieldName].ToString();
                        keys.Add(key);
                    }
                }
            }
            return keys;
        }
        #endregion
    }
}
